﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using SilverBullet.Manifest;

namespace Microsoft.Xna.Framework
{
    /// <summary>
	/// Provides basic graphics device initialization, game logic, and rendering code.
    /// </summary>
    public class Game : IDisposable
	{
		private ContentManager content;
		private GameTime gameTime;
		private IGraphicsDeviceManager graphicsDeviceManager;
		private IGraphicsDeviceService graphicsDeviceService;
		private TimeSpan inactiveSleepTime;
		private bool isActive;
		private bool inRun;
		private TimeSpan totalGameTime;

		#region Events
		/// <summary>
		/// Raised when the game gains focus.
		/// </summary>
		public event EventHandler<EventArgs> Activated;

		/// <summary>
		/// Raised when the game loses focus.
		/// </summary>
		public event EventHandler<EventArgs> Deactivated;

		/// <summary>
		/// Raised when the game is being disposed.
		/// </summary>
		public event EventHandler<EventArgs> Disposed;

		/// <summary>
		/// Raised when the game is exiting.
		/// </summary>
        public event EventHandler<EventArgs> Exiting;
		#endregion

		GraphicsDevice graphicsDevice;
        bool _initialized = false;

        GameLoop gameLoop;
        GameTime updateGameTime;
        GameTime drawGameTime;

		private List<IGameComponent> componentsNeedingInitialization;
		private List<IUpdateable> componentsToUpdate;
		private List<IDrawable> componentsToDraw;
		private List<IDrawable> currentlyDrawingComponents;
		private List<IUpdateable> currentlyUpdatingComponents;

        GameComponentCollection _gameComponentCollection = new GameComponentCollection();
        private GameServiceContainer _services;
        GameWindow _window;
        internal States _currentState = States.Idle;
        SolidColorBrush backgroundBrush = new SolidColorBrush(Colors.Black);
		private bool endRunRequired;
		private bool suppressDraw;
        internal enum States
        {
            Idle,
            Startup,
            Running,
            Done
        }

		#region Properties
		/// <summary>
		/// Gets the collection of GameComponents owned by the game.
		/// </summary>
		public GameComponentCollection Components
		{
			get { return _gameComponentCollection; }
		}

		/// <summary>
		/// Gets or sets the current ContentManager.
		/// </summary>
		public ContentManager Content
		{
			get { return this.content; }
			set
			{
				if (value == null)
				{
					throw new ArgumentNullException();
				}
				this.content = value;
			}
		}

		/// <summary>
		/// Gets the current GraphicsDevice.
		/// </summary>
		public GraphicsDevice GraphicsDevice
		{
			get
			{
				IGraphicsDeviceService graphicsDeviceService = this.graphicsDeviceService;
				if (graphicsDeviceService == null)
				{
					graphicsDeviceService = this.Services.GetService(typeof(IGraphicsDeviceService)) as IGraphicsDeviceService;
					if (graphicsDeviceService == null)
					{
						throw new InvalidOperationException(FrameworkResources.NoGraphicsDeviceService);
					}
				}
				return graphicsDeviceService.GraphicsDevice;
			}
		}

		/// <summary>
		/// Gets or sets the time to sleep when the game is inactive.
		/// </summary>
		public TimeSpan InactiveSleepTime
		{
			get { return this.inactiveSleepTime; }
			set
			{
				if (value < TimeSpan.Zero)
				{
					throw new ArgumentOutOfRangeException("value", FrameworkResources.InactiveSleepTimeCannotBeZero);
				}
				this.inactiveSleepTime = value;
			}
		}

        public bool IsActive
        {
            get { return true; }
        }

		public bool IsFixedTimeStep
		{
			get;
			set;
		}

        public bool IsMouseVisible
        {
            get;
            set;
        }

		/// <summary>
		/// Gets the GameServiceContainer holding all the service providers attached to the Game.
		/// </summary>
		public GameServiceContainer Services
		{
			get { return _services; }
		}

		public bool ShouldExit { get; set; }

		/// <summary>
		/// Gets or sets the target time between calls to Update when IsFixedTimeStep is true.
		/// </summary>
        public TimeSpan TargetElapsedTime
        {
            get
            {
				InitializeGameLoop();
                return gameLoop.TargetElapsedTime;
            }
            set
            {
				if (value <= TimeSpan.Zero)
				{
					throw new ArgumentOutOfRangeException("value", FrameworkResources.TargetElapsedCannotBeZero);
				}
				InitializeGameLoop();
				gameLoop.TargetElapsedTime = value;
            }
        }

		/// <summary>
		/// Gets the underlying operating system window.
		/// </summary>
		public GameWindow Window
		{
			get { return _window; }
		}
		#endregion

		#region Constructor
		/// <summary>
		/// Initializes a new instance of this class, which provides basic graphics device initialization, game logic, rendering code, and a game loop.
		/// </summary>
        public Game()
        {
			if (Discovery.Ready == false)
			{
				Discovery.Initialize();
			}
			this.gameTime = new GameTime();
			this.IsFixedTimeStep = true;

			this.componentsNeedingInitialization = new List<IGameComponent>();
			this.componentsToDraw = new List<IDrawable>();
			this.componentsToUpdate = new List<IUpdateable>();
			this.currentlyDrawingComponents = new List<IDrawable>();
			this.currentlyUpdatingComponents = new List<IUpdateable>();

			this._services = new GameServiceContainer();
			this._gameComponentCollection = new GameComponentCollection();
			this._gameComponentCollection.ComponentAdded += new EventHandler<GameComponentCollectionEventArgs>(_gameComponentCollection_ComponentAdded);
			this._gameComponentCollection.ComponentRemoved += new EventHandler<GameComponentCollectionEventArgs>(_gameComponentCollection_ComponentRemoved);
			this.content = new ContentManager(_services);
			this.totalGameTime = TimeSpan.Zero;
            _window = new SilverlightGameWindow();
            updateGameTime = new GameTime();
            drawGameTime = new GameTime();
//            sb.Completed += new EventHandler(sb_Completed);
            //this.Loaded += new RoutedEventHandler(Game_Loaded);
			InitializeGameLoop();
            _currentState = States.Startup;
        }

		/// <summary>
		/// Allows a Game to attempt to free resources and perform other cleanup operations before garbage collection reclaims the Game.
		/// </summary>
		~Game()
		{
			this.Dispose(false);
		}
		#endregion

		void _gameComponentCollection_ComponentAdded(object sender, GameComponentCollectionEventArgs e)
		{
			if (this.inRun)
			{
				e.GameComponent.Initialize();
			}
			else
			{
				componentsNeedingInitialization.Add(e.GameComponent);
			}

			IUpdateable gameComponent = e.GameComponent as IUpdateable;
			if (gameComponent != null)
			{
				int index = this.componentsToUpdate.BinarySearch(gameComponent, UpdateOrderComparer.Default);
				if (index < 0)
				{
					index = ~index;
					while ((index < this.componentsToUpdate.Count) && (this.componentsToUpdate[index].UpdateOrder == gameComponent.UpdateOrder))
					{
						index++;
					}
					this.componentsToUpdate.Insert(index, gameComponent);
					gameComponent.UpdateOrderChanged += new EventHandler<EventArgs>(this.gameComponent_UpdateOrderChanged);
				}
			}

			IDrawable item = e.GameComponent as IDrawable;
			if (item != null)
			{
				int num2 = this.componentsToDraw.BinarySearch(item, DrawOrderComparer.Default);
				if (num2 < 0)
				{
					num2 = ~num2;
					while ((num2 < this.componentsToDraw.Count) && (this.componentsToDraw[num2].DrawOrder == item.DrawOrder))
					{
						num2++;
					}
					this.componentsToDraw.Insert(num2, item);
					item.DrawOrderChanged += new EventHandler<EventArgs>(this.gameComponent_DrawOrderChanged);
				}
			}
		}

		void gameComponent_DrawOrderChanged(object sender, EventArgs e)
		{
			IDrawable item = sender as IDrawable;
			this.componentsToDraw.Remove(item);
			int index = this.componentsToDraw.BinarySearch(item, DrawOrderComparer.Default);
			if (index < 0)
			{
				index = ~index;
				while ((index < this.componentsToDraw.Count) && (this.componentsToDraw[index].DrawOrder == item.DrawOrder))
				{
					index++;
				}
				this.componentsToDraw.Insert(index, item);
			}
		}

		void gameComponent_UpdateOrderChanged(object sender, EventArgs e)
		{
			IUpdateable item = sender as IUpdateable;
			this.componentsToUpdate.Remove(item);
			int index = this.componentsToUpdate.BinarySearch(item, UpdateOrderComparer.Default);
			if (index < 0)
			{
				index = ~index;
				while ((index < this.componentsToUpdate.Count) && (this.componentsToUpdate[index].UpdateOrder == item.UpdateOrder))
				{
					index++;
				}
				this.componentsToUpdate.Insert(index, item);
			}
		}

		void _gameComponentCollection_ComponentRemoved(object sender, GameComponentCollectionEventArgs e)
		{
			if (!this.inRun)
			{
				this.componentsNeedingInitialization.Remove(e.GameComponent);
			}
			IUpdateable gameComponent = e.GameComponent as IUpdateable;
			if (gameComponent != null)
			{
				this.componentsToUpdate.Remove(gameComponent);
				gameComponent.UpdateOrderChanged -= new EventHandler<EventArgs>(this.gameComponent_UpdateOrderChanged);
			}
			IDrawable item = e.GameComponent as IDrawable;
			if (item != null)
			{
				this.componentsToDraw.Remove(item);
				item.DrawOrderChanged -= new EventHandler<EventArgs>(this.gameComponent_DrawOrderChanged);
			}
		}

		void Game_Loaded(object sender, RoutedEventArgs e)
        {
			//GraphicsDevice.Root = this;
			InitializeGameLoop();
		}

		void InitializeGameLoop()
		{
			if (gameLoop == null)
			{
				gameLoop = new GameLoop();
				gameLoop.Update += new EventHandler<SimpleEventArgs<TimeSpan>>(gameLoop_Update);
				gameLoop.Draw += new EventHandler<SimpleEventArgs<TimeSpan>>(gameLoop_Draw);
			}
		}

		void gameLoop_Draw(object sender, SimpleEventArgs<TimeSpan> e)
		{
			for (int i = 0; i < GraphicsDevice._allRenderTargets.Count; i++)
			{
				GraphicsDevice._allRenderTargets[i]._renderer.BeforeDraw();
			}
			Draw(drawGameTime);
			for (int i = 0; i < GraphicsDevice._allRenderTargets.Count; i++)
			{
				GraphicsDevice._allRenderTargets[i]._renderer.AfterDraw();
			}
		}

		void gameLoop_Update(object sender, SimpleEventArgs<TimeSpan> e)
		{
			if (!_initialized)
			{
				//GraphicsDevice.GraphicsDeviceManager.ApplyChanges();
				Initialize();
				RectangleGeometry r = new RectangleGeometry();
				r.Rect = new Rect(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
				GraphicsDevice.Root.Clip = r;
				_initialized = true;
				_currentState = States.Running;
				BeginRun();
			} 
			if (_currentState == States.Done) return;
			Update(updateGameTime);
		}

		public void AddFont(string fontName, string fontFamily)
		{
			FontFamily f = new FontFamily(fontFamily);
			TextBlock t = new TextBlock();
			//this.Children.Add(t);
			t.FontFamily = f;
			SpriteFont.AddFont(fontName, f);
		}

		#region Private Methods
		private void DeviceCreated(object sender, EventArgs e)
		{
			this.LoadContent();
		}

		private void DeviceDisposing(object sender, EventArgs e)
		{
			this.content.Unload();
			this.UnloadContent();
		}

		private void DeviceReset(object sender, EventArgs e)
		{
		}

		private void DeviceResetting(object sender, EventArgs e)
		{
		}
		#endregion

		#region Protected Methods
		/// <summary>
		/// Starts the drawing of a frame. This method is followed by calls to Draw and EndDraw.
		/// </summary>
        protected virtual bool BeginDraw()
        {
			if (IsFixedTimeStep && updateGameTime.IsRunningSlowly)
			{
				return false;
			}

			return graphicsDeviceManager.BeginDraw();
        }

		/// <summary>
		/// Called after all components are initialized but before the first update in the game loop.
		/// </summary>
		protected virtual void BeginRun() { }

		/// <summary>
		/// Releases all resources used by the Game class.
		/// </summary>
		/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				lock (this)
				{
					IGameComponent[] array = new IGameComponent[this._gameComponentCollection.Count];
					this._gameComponentCollection.CopyTo(array, 0);

					for (int i = 0; i < array.Length; i++)
					{
						IDisposable disposable = array[i] as IDisposable;
						if (disposable != null)
						{
							disposable.Dispose();
						}
					}

					IDisposable graphicsDeviceManager = this.graphicsDeviceManager as IDisposable;
					if (graphicsDeviceManager != null)
					{
						graphicsDeviceManager.Dispose();

						this.graphicsDeviceService.DeviceCreated -= new EventHandler<EventArgs>(this.DeviceCreated);
						this.graphicsDeviceService.DeviceResetting -= new EventHandler<EventArgs>(this.DeviceResetting);
						this.graphicsDeviceService.DeviceReset -= new EventHandler<EventArgs>(this.DeviceReset);
						this.graphicsDeviceService.DeviceDisposing -= new EventHandler<EventArgs>(this.DeviceDisposing);
					}

					EventHandler<EventArgs> disposedHandler = this.Disposed;
					if (disposedHandler != null)
					{
						disposedHandler(this, EventArgs.Empty);
					}
				}
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="gameTime">Time passed since the last call to Draw.</param>
		protected virtual void Draw(GameTime gameTime)
		{
			for (int i = 0; i < componentsToDraw.Count; i++)
			{
				currentlyDrawingComponents.Add(componentsToDraw[i]);
			}

			for (int i = 0; i < currentlyDrawingComponents.Count; i++)
			{
				if (currentlyDrawingComponents[i].Visible)
				{
					currentlyDrawingComponents[i].Draw(gameTime);
				}
			}

			currentlyDrawingComponents.Clear();
		}

		/// <summary>
		/// Ends the drawing of a frame. This method is preceeded by calls to Draw and BeginDraw.
		/// </summary>
        protected virtual void EndDraw()
        {
			this.graphicsDeviceManager.EndDraw();
        }

		/// <summary>
		/// Called after the game loop has stopped running before exiting.
		/// </summary>
		protected virtual void EndRun()
		{
		}

		/// <summary>
		/// Called after the Game and GraphicsDevice are created, but before LoadContent.
		/// </summary>
		protected virtual void Initialize()
		{
			this.graphicsDeviceService = this.Services.GetService(typeof(IGraphicsDeviceService)) as IGraphicsDeviceService;
			if (this.graphicsDeviceService != null)
			{
				this.graphicsDeviceService.DeviceCreated += new EventHandler<EventArgs>(this.DeviceCreated);
				this.graphicsDeviceService.DeviceResetting += new EventHandler<EventArgs>(this.DeviceResetting);
				this.graphicsDeviceService.DeviceReset += new EventHandler<EventArgs>(this.DeviceReset);
				this.graphicsDeviceService.DeviceDisposing += new EventHandler<EventArgs>(this.DeviceDisposing);
			}

			while (this.componentsNeedingInitialization.Count > 0)
			{
				this.componentsNeedingInitialization[0].Initialize();
				this.componentsNeedingInitialization.RemoveAt(0);
			}

			if ((this.graphicsDeviceService != null) && (this.graphicsDeviceService.GraphicsDevice != null))
			{
				this.LoadContent();
			}
		}

		/// <summary>
		/// 
		/// </summary>
		protected virtual void LoadContent()
		{
		}

		/// <summary>
		/// Raises the Activated event. Override this method to add code to handle when the game gains focus.
		/// </summary>
		/// <param name="sender">The Game.</param>
		/// <param name="e">Arguments for the Activated event.</param>
        protected virtual void OnActivated(object sender, EventArgs e)
        {
			EventHandler<EventArgs> activatedHandler = this.Activated;
			if (activatedHandler != null)
			{
				activatedHandler(sender, e);
			}
        }

		/// <summary>
		/// Raises the Deactivated event. Override this method to add code to handle when the game loses focus.
		/// </summary>
		/// <param name="sender">The Game.</param>
		/// <param name="e">Arguments for the Deactivated event.</param>
        protected virtual void OnDeactivated(object sender, EventArgs e)
        {
			EventHandler<EventArgs> deactivatedHandler = this.Deactivated;
			if (deactivatedHandler != null)
			{
				deactivatedHandler(sender, e);
			}
        }

		/// <summary>
		/// Raises the Exiting event. Override this method to add code to handle when the game is exiting.
		/// </summary>
		/// <param name="sender">The Game.</param>
		/// <param name="e">Arguments for the Exiting event.</param>
		protected virtual void OnExiting(object sender, EventArgs e)
		{
			EventHandler<EventArgs> exitingHandler = this.Exiting;
			if (exitingHandler != null)
			{
				exitingHandler(sender, e);
			}
		}

		/// <summary>
		/// This is used to display an error message if there is no suitable graphics device or sound card.
		/// </summary>
		/// <param name="exception">The exception to display.</param>
		protected virtual bool ShowMissingRequirementMessage(Exception exception)
		{
			string noAudioHardware;
			if (exception is NoSuitableGraphicsDeviceException)
			{
				noAudioHardware = FrameworkResources.NoSuitableGraphicsDevice + "\n\n" + exception.Message;
			}
			else if (exception is NoAudioHardwareException)
			{
				noAudioHardware = FrameworkResources.NoAudioHardware;
			}
			else
			{
				return false;
			}
			MessageBox.Show(noAudioHardware, this.Window.Title, MessageBoxButton.OK);
			return true;
		}

		/// <summary>
		/// Called when graphics resources need to be unloaded. Override this method to unload any game-specific graphics resources.
		/// </summary>
		protected virtual void UnloadContent()
		{
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="gameTime">Time passed since the last call to Update.</param>
		protected virtual void Update(GameTime gameTime)
		{
			for (int i = 0; i < this.componentsToUpdate.Count; i++)
			{
				this.currentlyUpdatingComponents.Add(this.componentsToUpdate[i]);
			}

			for (int i = 0; i < this.currentlyUpdatingComponents.Count; i++)
			{
				if (this.currentlyUpdatingComponents[i].Enabled)
				{
					this.currentlyUpdatingComponents[i].Update(gameTime);
				}
			}

			this.currentlyUpdatingComponents.Clear();
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Immediately releases the unmanaged resources used by this object.
		/// </summary>
		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Exits the game.
		/// </summary>
		public void Exit()
		{
			//this.exitRequested = true;
			//this.host.Exit();
			if (this.inRun && this.endRunRequired)
			{
				this.EndRun();
				this.inRun = false;
			}

			_currentState = States.Done;
			EventHandler<EventArgs> exitHandler = Exiting;
			if (exitHandler != null)
			{
				exitHandler(this, EventArgs.Empty);
			}
			gameLoop.Dispose();
		}

		/// <summary>
		/// Call this method to initialize the game, begin running the game loop, and start processing events for the game.
		/// </summary>
		public void Run()
		{
			try
			{
				if (inRun)
				{
					throw new InvalidOperationException("Run Method called more than once");
				}
				inRun = true;
				this.graphicsDeviceManager = this.Services.GetService(typeof(IGraphicsDeviceManager)) as IGraphicsDeviceManager;
				if (this.graphicsDeviceManager != null)
				{
					this.graphicsDeviceManager.CreateDevice();
				}
				this.Initialize();
				this.inRun = true;
				this.BeginRun();
				this.gameTime.ElapsedGameTime = TimeSpan.Zero;
				this.gameTime.TotalGameTime = this.totalGameTime;
				this.gameTime.IsRunningSlowly = false;
				this.Update(this.gameTime);

				this.endRunRequired = true;
			}
			catch (NoSuitableGraphicsDeviceException exception)
			{
				if (!this.ShowMissingRequirementMessage(exception))
				{
					throw;
				}
			}
			catch (NoAudioHardwareException exception2)
			{
				if (!this.ShowMissingRequirementMessage(exception2))
				{
					throw;
				}
			}
			finally
			{
				if (!this.endRunRequired)
				{
					this.inRun = false;
				}
			}			
		}

		/// <summary>
		/// Run the game through what would happen in a single tick of the game clock; this method is designed for debugging only.
		/// </summary>
		public void RunOneFrame()
		{
			//! TODO: implement
		}

		/// <summary>
		/// Prevents calls to Draw until the next Update.
		/// </summary>
		public void SuppressDraw()
		{
			this.suppressDraw = true;
		}

		/// <summary>
		/// Updates the game's clock and calls Update and Draw.
		/// </summary>
		public void Tick()
		{
			if (!this.ShouldExit)
			{
				if (!this.IsActive)
				{
					Thread.Sleep((int)this.inactiveSleepTime.TotalMilliseconds);
				}


			}
		}
		#endregion

		#region Comparers
		private class DrawOrderComparer : IComparer<IDrawable>
		{
			public static DrawOrderComparer Default = new DrawOrderComparer();

			public int Compare(IDrawable x, IDrawable y)
			{
				if (x.DrawOrder > y.DrawOrder)
					return 1;
				if (x.DrawOrder < y.DrawOrder)
					return -1;
				return 0;
			}
		}

		private class UpdateOrderComparer : IComparer<IUpdateable>
		{
			public static UpdateOrderComparer Default = new UpdateOrderComparer();

			public int Compare(IUpdateable x, IUpdateable y)
			{
				if (x.UpdateOrder > y.UpdateOrder)
					return 1;
				if (x.UpdateOrder < y.UpdateOrder)
					return -1;
				return 0;
			}
		}
		#endregion
	}
}
